home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
748
/
748.xpi
/
components
/
greasemonkey.js
< prev
Wrap
Text File
|
2010-02-11
|
18KB
|
556 lines
const CLASSNAME = "GM_GreasemonkeyService";
const CONTRACTID = "@greasemonkey.mozdev.org/greasemonkey-service;1";
const CID = Components.ID("{77bf3650-1cd6-11da-8cd6-0800200c9a66}");
const Cc = Components.classes;
const Ci = Components.interfaces;
const appSvc = Cc["@mozilla.org/appshell/appShellService;1"]
.getService(Ci.nsIAppShellService);
const gmSvcFilename = Components.stack.filename;
const maxJSVersion = (function getMaxJSVersion() {
var appInfo = Components
.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
var versionChecker = Components
.classes["@mozilla.org/xpcom/version-comparator;1"]
.getService(Components.interfaces.nsIVersionComparator);
// Firefox 3.5 and higher supports 1.8.
if (versionChecker.compare(appInfo.version, "3.5") >= 0) {
return "1.8";
}
// Everything else supports 1.6.
return "1.6";
})();
function alert(msg) {
Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService)
.alert(null, "Greasemonkey alert", msg);
}
// Examines the stack to determine if an API should be callable.
function GM_apiLeakCheck(apiName) {
var stack = Components.stack;
do {
// Valid stack frames for GM api calls are: native and js when coming from
// chrome:// URLs and the greasemonkey.js component's file:// URL.
if (2 == stack.language) {
// NOTE: In FF 2.0.0.0, I saw that stack.filename can be null for JS/XPCOM
// services. This didn't happen in FF 2.0.0.11; I'm not sure when it
// changed.
if (stack.filename != null &&
stack.filename != gmSvcFilename &&
stack.filename.substr(0, 6) != "chrome") {
GM_logError(new Error("Greasemonkey access violation: unsafeWindow " +
"cannot call " + apiName + "."));
return false;
}
}
stack = stack.caller;
} while (stack);
return true;
}
var greasemonkeyService = {
_config: null,
get config() {
if (!this._config)
this._config = new Config();
return this._config;
},
browserWindows: [],
// nsISupports
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIObserver) &&
!aIID.equals(Ci.nsISupports) &&
!aIID.equals(Ci.nsISupportsWeakReference) &&
!aIID.equals(Ci.gmIGreasemonkeyService) &&
!aIID.equals(Ci.nsIWindowMediatorListener) &&
!aIID.equals(Ci.nsIContentPolicy)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return this;
},
// nsIObserver
observe: function(aSubject, aTopic, aData) {
if (aTopic == "app-startup") {
this.startup();
}
},
// gmIGreasemonkeyService
registerBrowser: function(browserWin) {
var existing;
for (var i = 0; existing = this.browserWindows[i]; i++) {
if (existing == browserWin) {
// NOTE: Unlocalised strings
throw new Error("Browser window has already been registered.");
}
}
this.browserWindows.push(browserWin);
},
unregisterBrowser: function(browserWin) {
var existing;
for (var i = 0; existing = this.browserWindows[i]; i++) {
if (existing == browserWin) {
this.browserWindows.splice(i, 1);
return;
}
}
throw new Error("Browser window is not registered.");
},
domContentLoaded: function(wrappedContentWin, chromeWin) {
var unsafeWin = wrappedContentWin.wrappedJSObject;
var unsafeLoc = new XPCNativeWrapper(unsafeWin, "location").location;
var href = new XPCNativeWrapper(unsafeLoc, "href").href;
var scripts = this.initScripts(href);
if (scripts.length > 0) {
this.injectScripts(scripts, href, unsafeWin, chromeWin);
}
},
startup: function() {
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://global/content/XPCNativeWrapper.js");
loader.loadSubScript("chrome://greasemonkey/content/prefmanager.js");
loader.loadSubScript("chrome://greasemonkey/content/utils.js");
loader.loadSubScript("chrome://greasemonkey/content/config.js");
loader.loadSubScript("chrome://greasemonkey/content/convert2RegExp.js");
loader.loadSubScript("chrome://greasemonkey/content/miscapis.js");
loader.loadSubScript("chrome://greasemonkey/content/xmlhttprequester.js");
//loggify(this, "GM_GreasemonkeyService");
},
shouldLoad: function(ct, cl, org, ctx, mt, ext) {
var ret = Ci.nsIContentPolicy.ACCEPT;
// block content detection of greasemonkey by denying GM
// chrome content, unless loaded from chrome
if (org && org.scheme != "chrome" && cl.scheme == "chrome" &&
cl.host == "greasemonkey") {
return Ci.nsIContentPolicy.REJECT_SERVER;
}
// don't intercept anything when GM is not enabled
if (!GM_getEnabled()) {
return ret;
}
// don't interrupt the view-source: scheme
// (triggered if the link in the error console is clicked)
if ("view-source" == cl.scheme) {
return ret;
}
if (ct == Ci.nsIContentPolicy.TYPE_DOCUMENT &&
cl.spec.match(/\.user\.js$/)) {
dump("shouldload: " + cl.spec + "\n");
dump("ignorescript: " + this.ignoreNextScript_ + "\n");
if (!this.ignoreNextScript_) {
if (!this.isTempScript(cl)) {
var winWat = Cc["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Ci.nsIWindowWatcher);
if (winWat.activeWindow && winWat.activeWindow.GM_BrowserUI) {
winWat.activeWindow.GM_BrowserUI.startInstallScript(cl);
ret = Ci.nsIContentPolicy.REJECT_REQUEST;
}
}
}
}
this.ignoreNextScript_ = false;
return ret;
},
shouldProcess: function(ct, cl, org, ctx, mt, ext) {
return Ci.nsIContentPolicy.ACCEPT;
},
ignoreNextScript: function() {
dump("ignoring next script...\n");
this.ignoreNextScript_ = true;
},
isTempScript: function(uri) {
if (uri.scheme != "file") {
return false;
}
var fph = Components.classes["@mozilla.org/network/protocol;1?name=file"]
.getService(Ci.nsIFileProtocolHandler);
var file = fph.getFileFromURLSpec(uri.spec);
var tmpDir = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)
.get("TmpD", Components.interfaces.nsILocalFile);
return file.parent.equals(tmpDir) && file.leafName != "newscript.user.js";
},
initScripts: function(url) {
function testMatch(script) {
return script.enabled && script.matchesURL(url);
}
return GM_getConfig().getMatchingScripts(testMatch);
},
injectScripts: function(scripts, url, unsafeContentWin, chromeWin) {
var sandbox;
var script;
var logger;
var console;
var storage;
var xmlhttpRequester;
var resources;
var safeWin = new XPCNativeWrapper(unsafeContentWin);
var safeDoc = safeWin.document;
// detect and grab reference to firebug console and context, if it exists
var firebugConsole = this.getFirebugConsole(unsafeContentWin, chromeWin);
for (var i = 0; script = scripts[i]; i++) {
sandbox = new Components.utils.Sandbox(safeWin);
logger = new GM_ScriptLogger(script);
console = firebugConsole ? firebugConsole : new GM_console(script);
storage = new GM_ScriptStorage(script);
xmlhttpRequester = new GM_xmlhttpRequester(unsafeContentWin,
appSvc.hiddenDOMWindow);
resources = new GM_Resources(script);
sandbox.window = safeWin;
sandbox.document = sandbox.window.document;
sandbox.unsafeWindow = unsafeContentWin;
// hack XPathResult since that is so commonly used
sandbox.XPathResult = Ci.nsIDOMXPathResult;
// add our own APIs
sandbox.GM_addStyle = function(css) { GM_addStyle(safeDoc, css) };
sandbox.GM_log = GM_hitch(logger, "log");
sandbox.console = console;
sandbox.GM_setValue = GM_hitch(storage, "setValue");
sandbox.GM_getValue = GM_hitch(storage, "getValue");
sandbox.GM_deleteValue = GM_hitch(storage, "deleteValue");
sandbox.GM_listValues = GM_hitch(storage, "listValues");
sandbox.GM_getResourceURL = GM_hitch(resources, "getResourceURL");
sandbox.GM_getResourceText = GM_hitch(resources, "getResourceText");
sandbox.GM_openInTab = GM_hitch(this, "openInTab", safeWin, chromeWin);
sandbox.GM_xmlhttpRequest = GM_hitch(xmlhttpRequester,
"contentStartRequest");
sandbox.GM_registerMenuCommand = GM_hitch(this,
"registerMenuCommand",
unsafeContentWin);
sandbox.__proto__ = safeWin;
var contents = script.textContent;
var requires = [];
var offsets = [];
var offset = 0;
script.requires.forEach(function(req){
var contents = req.textContent;
var lineCount = contents.split("\n").length;
requires.push(contents);
offset += lineCount;
offsets.push(offset);
});
script.offsets = offsets;
var scriptSrc = "\n" + // error line-number calculations depend on these
requires.join("\n") +
"\n" +
contents +
"\n";
if (!script.unwrap)
scriptSrc = "(function(){"+ scriptSrc +"})()";
if (!this.evalInSandbox(scriptSrc, url, sandbox, script) && script.unwrap)
this.evalInSandbox("(function(){"+ scriptSrc +"})()",
url, sandbox, script); // wrap anyway on early return
}
},
registerMenuCommand: function(unsafeContentWin, commandName, commandFunc,
accelKey, accelModifiers, accessKey) {
if (!GM_apiLeakCheck("GM_registerMenuCommand")) {
return;
}
var command = {name: commandName,
accelKey: accelKey,
accelModifiers: accelModifiers,
accessKey: accessKey,
doCommand: commandFunc,
window: unsafeContentWin };
for (var i = 0; i < this.browserWindows.length; i++) {
this.browserWindows[i].registerMenuCommand(command);
}
},
openInTab: function(safeContentWin, chromeWin, url) {
if (!GM_apiLeakCheck("GM_openInTab")) {
return undefined;
}
var info = Cc["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
if (parseFloat(info.version, 10) < 3.0) {
// Pre FF 3.0 wants the URL as the second argument.
var newTab = chromeWin.openNewTabWith(
url, safeContentWin.document.location.href, null, null, null, null);
} else {
// Post FF 3.0 wants the document as the second argument.
var newTab = chromeWin.openNewTabWith(
url, safeContentWin.document, null, null, null, null);
}
// Source:
// http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4448
var newWindow = chromeWin.gBrowser
.getBrowserForTab(newTab)
.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
return newWindow;
},
evalInSandbox: function(code, codebase, sandbox, script) {
if (!(Components.utils && Components.utils.Sandbox)) {
var e = new Error("Could not create sandbox.");
GM_logError(e, 0, e.fileName, e.lineNumber);
return true;
}
try {
// workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=307984
var lineFinder = new Error();
Components.utils.evalInSandbox(code, sandbox, maxJSVersion);
} catch (e) { // catches errors while running the script code
try {
if (e && "return not in function" == e.message)
return false; // means this script depends on the function enclosure
// try to find the line of the actual error line
var line = e && e.lineNumber;
if (4294967295 == line) {
// Line number is reported as max int in edge cases. Sometimes
// the right one is in the "location", instead. Look there.
if (e.location && e.location.lineNumber) {
line = e.location.lineNumber;
} else {
// Reporting maxint is useless, if we couldn't find it in location
// either, forget it. A value of 0 isn't shown in the console.
line = 0;
}
}
if (line) {
var err = this.findError(script, line - lineFinder.lineNumber - 1);
GM_logError(
e, // error obj
0, // 0 = error (1 = warning)
err.uri,
err.lineNumber
);
} else {
GM_logError(
e, // error obj
0, // 0 = error (1 = warning)
script.fileURL,
0
);
}
} catch (e) { // catches errors we cause trying to inform the user
// Do nothing. More importantly: don't stop script incovation sequence.
}
}
return true; // did not need a (function() {...})() enclosure.
},
findError: function(script, lineNumber){
var start = 0;
var end = 1;
for (var i = 0; i < script.offsets.length; i++) {
end = script.offsets[i];
if (lineNumber < end) {
return {
uri: script.requires[i].fileURL,
lineNumber: (lineNumber - start)
};
}
start = end;
}
return {
uri: script.fileURL,
lineNumber: (lineNumber - end)
};
},
getFirebugConsole: function(unsafeContentWin, chromeWin) {
// If we can't find this object, there's no chance the rest of this
// function will work.
if ('undefined'==typeof chromeWin.Firebug) return null;
try {
chromeWin = chromeWin.top;
var fbVersion = parseFloat(chromeWin.Firebug.version, 10);
var fbConsole = chromeWin.Firebug.Console;
var fbContext = chromeWin.TabWatcher &&
chromeWin.TabWatcher.getContextByWindow(unsafeContentWin);
// Firebug 1.4 will give no context, when disabled for the current site.
// We can't run that way.
if ('undefined'==typeof fbContext) {
return null;
}
function findActiveContext() {
for (var i=0; i<fbContext.activeConsoleHandlers.length; i++) {
if (fbContext.activeConsoleHandlers[i].window == unsafeContentWin) {
return fbContext.activeConsoleHandlers[i];
}
}
return null;
}
try {
if (!fbConsole.isEnabled(fbContext)) return null;
} catch (e) {
// FB 1.1 can't be enabled/disabled. Function to check doesn't exist.
// Silently ignore.
}
if (fbVersion < 1.2) {
return new chromeWin.FirebugConsole(fbContext, unsafeContentWin);
} else if (1.2 == fbVersion) {
var safeWin = new XPCNativeWrapper(unsafeContentWin);
if (fbContext.consoleHandler) {
for (var i = 0; i < fbContext.consoleHandler.length; i++) {
if (fbContext.consoleHandler[i].window == safeWin) {
return fbContext.consoleHandler[i].handler;
}
}
}
var dummyElm = safeWin.document.createElement("div");
dummyElm.setAttribute("id", "_firebugConsole");
safeWin.document.documentElement.appendChild(dummyElm);
chromeWin.Firebug.Console.injector.addConsoleListener(fbContext, safeWin);
dummyElm.parentNode.removeChild(dummyElm);
return fbContext.consoleHandler.pop().handler;
} else if (1.3 == fbVersion || 1.4 == fbVersion || 1.5 == fbVersion) {
fbConsole.injector.attachIfNeeded(fbContext, unsafeContentWin);
return findActiveContext();
}
} catch (e) {
dump('Greasemonkey getFirebugConsole() error:\n'+uneval(e)+'\n');
}
return null;
}
};
greasemonkeyService.wrappedJSObject = greasemonkeyService;
/**
* XPCOM Registration goop
*/
var Module = new Object();
Module.registerSelf = function(compMgr, fileSpec, location, type) {
compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.registerFactoryLocation(CID,
CLASSNAME,
CONTRACTID,
fileSpec,
location,
type);
var catMgr = Cc["@mozilla.org/categorymanager;1"]
.getService(Ci.nsICategoryManager);
catMgr.addCategoryEntry("app-startup",
CLASSNAME,
CONTRACTID,
true,
true);
catMgr.addCategoryEntry("content-policy",
CONTRACTID,
CONTRACTID,
true,
true);
};
Module.getClassObject = function(compMgr, cid, iid) {
if (!cid.equals(CID)) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
if (!iid.equals(Ci.nsIFactory)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return Factory;
};
Module.canUnload = function(compMgr) {
return true;
};
var Factory = new Object();
Factory.createInstance = function(outer, iid) {
if (outer != null) {
throw Components.results.NS_ERROR_NO_AGGREGATION;
}
return greasemonkeyService;
};
function NSGetModule(compMgr, fileSpec) {
return Module;
}
//loggify(Module, "greasemonkeyService:Module");
//loggify(Factory, "greasemonkeyService:Factory");